﻿using System;
using System.ComponentModel.DataAnnotations;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Configuration;

namespace PoliticalClassifier.Web.Validation
{
	/// <summary>
	/// Specifies that a data field value is a file.
	/// </summary>
	/// <remarks>
	/// This attribute is used to mark a field, parameter, or property as a posted file.  It then validates that the marked field, parameter, or property contains a file that conforms to the specified set of attributes.
	/// </remarks>
	[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property)]
	public class FileAttribute : ValidationAttribute
	{
		#region Fields

		private static readonly string _defaultErrorMessage = "{0} is not a valid file.";

		#endregion

		#region Constructors

		/// <summary>
		/// Creates a <see cref="FileAttribute" /> with default settings.
		/// </summary>
		/// <remarks>
		/// The default constructor will initialize the new <see cref="FileAttribute" /> with the following default settings:
		///		<list type="bullet">
		///			<item>
		///				<term><see cref="ValidationAttribute.ErrorMessage" /></term>
		///				<description><see cref="ValidationAttribute.ErrorMessage" /> is set to <see cref="_defaultErrorMessage" />.</description>
		///			</item>
		///			<item>
		///				<term><see cref="Extensions" /></term>
		///				<description><see cref="Extensions" /> is set to <c>null</c>.</description>
		///			</item>
		///			<item>
		///				<term><see cref="MinSize" /></term>
		///				<description><see cref="MinSize" /> is set to <c>0</c>.</description>
		///			</item>
		///			<item>
		///				<term><see cref="MaxSize" /></term>
		///				<description><see cref="MaxSize" /> is set to the configured value of the system.web/httpRuntime/MaxRequestLength from the web.config.</description>
		///			</item>
		///		</list>
		/// </remarks>
		public FileAttribute()
			: base(_defaultErrorMessage)
		{
			MinSize = 0;
			MaxSize = ((HttpRuntimeSection) ConfigurationManager.GetSection("system.web/httpRuntime")).MaxRequestLength * 1024;
		}


		/// <summary>
		/// Creates a <see cref="FileAttribute" /> with a specified list of accepted file extensions.
		/// </summary>
		/// <param name="extensions">The list of valid file extensions.</param>
		/// <remarks>
		/// This constructor will initialize the new <see cref="FileAttribute" /> with the following default settings:
		///		<list type="bullet">
		///			<item>
		///				<term><see cref="ValidationAttribute.ErrorMessage" /></term>
		///				<description><see cref="ValidationAttribute.ErrorMessage" /> is set to <see cref="_defaultErrorMessage" />.</description>
		///			</item>
		///			<item>
		///				<term><see cref="Extensions" /></term>
		///				<description><see cref="Extensions" /> is set to <paramref name="extensions" />.</description>
		///			</item>
		///			<item>
		///				<term><see cref="MinSize" /></term>
		///				<description><see cref="MinSize" /> is set to <c>0</c>.</description>
		///			</item>
		///			<item>
		///				<term><see cref="MaxSize" /></term>
		///				<description><see cref="MaxSize" /> is set to the configured value of the system.web/httpRuntime/MaxRequestLength from the web.config.</description>
		///			</item>
		///		</list>
		/// </remarks>
		public FileAttribute(params string[] extensions)
			: base(_defaultErrorMessage)
		{
			Extensions = extensions;
			MinSize = 0;
			MaxSize = ((HttpRuntimeSection) ConfigurationManager.GetSection("system.web/httpRuntime")).MaxRequestLength * 1024;
		}

		#endregion

		#region Methods

		/// <summary>
		/// Tests whether a posted file is valid.
		/// </summary>
		/// <remarks>
		///		<para>Uses the configured settings to validate the posted file.  The file must meet the following criteria:</para>
		///		<list type="bullet">
		///			<item>
		///				<description><paramref name="value" /> must not be <c>null</c>.</description>
		///			</item>
		///			<item>
		///				<description><paramref name="value" /> must be convertable to <see cref="HttpPostedFileBase" />.</description>
		///			</item>
		///			<item>
		///				<description>The posted file's content length must be greater than <c>0</c>.</description>
		///			</item>
		///			<item>
		///				<description>The posted file's content length must be greater than or equal to <see cref="MinSize" />.</description>
		///			</item>
		///			<item>
		///				<description>The posted file's content length must be less than or equal to <see cref="MaxSize" />.</description>
		///			</item>
		///			<item>
		///				<description>The posted file's extension must be contained within <see cref="Extensions" />.</description>
		///			</item>
		///		</list>
		///		<para>If the posted file meets all of the above criteria, <see cref="IsValid(object)" /> will return <c>true</c>.  Otherwise, it will return <c>false</c>.</para>
		/// </remarks>
		/// <param name="value">The object to validate.</param>
		/// <returns>True/false indicating whether the tested <paramref name="value" /> is a valid file.</returns>
		public override bool IsValid(object value)
		{
			var file = value as HttpPostedFileBase;

			// Verify uploaded file
			if (file == null)
				return false;

			// Verify file size
			if ((file.ContentLength <= 0) || (file.ContentLength < MinSize) || (file.ContentLength > MaxSize))
				return false;

			// Verify file extension
			if ((Extensions != null) && Extensions.Any())
				return Extensions.Contains(Path.GetExtension(file.FileName));

			return true;
		}

		#endregion

		#region Properties

		/// <summary>
		/// Accepted file extensions.
		/// </summary>
		/// <value>
		/// An array of accepted file extensions.  If this value is null, all extensions are accepted.
		/// </value>
		public string[] Extensions { get; set; }


		/// <summary>
		/// Maximum file size (in bytes).
		/// </summary>
		/// <value>
		/// The maximum allowed file size (in bytes).
		/// </value>
		public int MaxSize { get; set; }


		/// <summary>
		/// Minimum file size (in bytes).
		/// </summary>
		/// <value>
		/// The minumum allowed file size (in bytes).
		/// </value>
		public int MinSize { get; set; }

		#endregion
	}
}